using System.Collections.Generic;
using NHibernate;
using Spring.Data.NHibernate.Generic;

namespace Common.Dao
{
    /// <summary>
    /// Common Data Access Object implementation, provide common code for data access layer such as 
    /// get, update, delete, query entities
    /// </summary>
    /// <typeparam name="TE">
    /// The target entity type that this DAO will manipulate on
    /// </typeparam>
    /// <typeparam name="TK">
    /// The target entity key type
    /// </typeparam>
    public class CommonDao<TE, TK> : HibernateDaoSupportEx, ICommonDao<TE, TK>
    {
        /// <summary>
        /// The entity name that th DAO manipulate on.
        /// </summary>
        protected string EntityName;

        /// <summary>
        /// Select all hql.
        /// </summary>
        protected string HqlSelectAll;

        /// <summary>
        /// Get count hql.
        /// </summary>
        protected string HqlCount;

        /// <summary>
        /// Delete by id hql.
        /// </summary>
        protected string HqlDelete;

        /// <summary>
        /// Delete all hql.
        /// </summary>
        protected string HqlDeleteAll;

        /// <summary>
        /// Initializes a new instance of the <see cref="CommonDao{TE, TK}"/> class.
        /// The default entity name is the type short name.
        /// </summary>
        public CommonDao()
            : this(typeof(TE).Name)
        {            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="CommonDao{TE, TK}"/> class.
        /// </summary>
        /// <param name="entityName">
        /// The entity name to pass on.
        /// </param>
        public CommonDao(string entityName)
        {
            this.EntityName = entityName;
            this.InitHql();
        }

        /// <summary>
        /// Initialize cached HQL statements.
        /// </summary>
        protected virtual void InitHql() {
            this.HqlCount = "select count(*) from " + this.EntityName + "%s";
            this.HqlDelete = "delete " + this.EntityName + " where id=?";
            this.HqlDeleteAll = "delete " + this.EntityName;
            this.HqlSelectAll = "from " + this.EntityName;
        }        

        /// <summary>
        /// Gets entity by the key.
        /// </summary>
        /// <param name="key">
        /// The specific key.
        /// </param>
        /// <returns>
        /// The matched entity.
        /// </returns>
        public virtual TE Get(TK key)
        {
            return this.GetHibernateTemplateEx().Get<TE>(key);
        }

        /// <summary>
        /// Gets entity by the key with specific lock mode.
        /// </summary>
        /// <param name="key">
        /// The specific key.
        /// </param>
        /// /// <param name="lockMode">
        /// The specific lock mode.
        /// </param>
        /// <returns>
        /// The entity matched input key.
        /// </returns>
        public virtual TE Get(TK key, LockMode lockMode)
        {
            return this.GetHibernateTemplateEx().Get<TE>(key, lockMode);
        }

        /// <summary>
        /// Save an entity to database.
        /// </summary>
        /// <param name="inst">
        /// The entity will be saved.
        /// </param>
        /// <returns>
        /// The key of the entity.
        /// </returns>
        public virtual TK Save(TE inst)
        {
            return (TK)this.GetHibernateTemplateEx().Save(inst);
        }

        /// <summary>
        /// Save or update an entity to database.
        /// </summary>
        /// <param name="inst">
        /// The entity will be saved/updated.
        /// </param>
        public virtual void SaveOrUpdate(TE inst)
        {
            this.GetHibernateTemplateEx().SaveOrUpdate(inst);
        }

        /// <summary>
        /// Save or update a list of entities.
        /// </summary>
        /// <param name="instList">
        /// The entity list.
        /// </param>
        public virtual void SaveOrUpdate(ICollection<TE> instList)
        {
            var template = this.GetHibernateTemplateEx();
            foreach (var inst in instList)
            {
                template.SaveOrUpdate(inst);
            }
        }

        /// <summary>
        /// Find a list of matched entities by providing an hql and its parameters.
        /// </summary>
        /// <param name="query">
        /// The hql query.
        /// </param>
        /// <param name="paras">
        /// The query parameters.
        /// </param>
        /// <returns>
        /// The matched entities.
        /// </returns>
        public virtual IList<TE> Find(string query, params object[] paras)
        {
            return this.GetHibernateTemplateEx().Find<TE>(query, paras);
        }

        /// <summary>
        /// Find a list of matched entities by providing a LINQ query 
        /// delegate and its parameters.
        /// </summary>
        /// <param name="linqQuery">
        /// The <see cref="LinqQuery{T,TRet}"/> delegate
        /// </param>
        /// <param name="paras">
        /// The query parameters.
        /// </param>
        /// <returns>
        /// The matched entities.
        /// </returns>
        public virtual IList<TE> Find(LinqQuery<TE, TE> linqQuery, params object[] paras)
        {
            return this.GetHibernateTemplateEx().Find(linqQuery, -1, -1, paras);
        }

        /// <summary>
        /// Find a list of matched entities by providing an hql and its named parameters
        /// values from a value object.
        /// </summary>
        /// <param name="query">
        /// The hql query.
        /// </param>
        /// <param name="valueObject">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// The matched entities.
        /// </returns>
        public virtual IList<TE> FindByValObj(string query, object valueObject)
        {
            return this.GetHibernateTemplateEx().FindByValueObject<TE>(query, valueObject);
        }

        /// <summary>
        /// Find a list of entities by provide a named HQL and its parameters.
        /// </summary>
        /// <param name="queryName">
        /// The HQL query name.
        /// </param>
        /// <param name="paras">
        /// The query parameters.
        /// </param>
        /// <returns>
        /// The matched entities.
        /// </returns>
        public virtual IList<TE> FindByNamedQuery(string queryName, params object[] paras)
        {
            return this.GetHibernateTemplateEx().FindByNamedQuery<TE>(queryName, paras);
        }

        /// <summary>
        /// Find a list of entities by provide a named HQL and 
        /// parameter values from a value object
        /// </summary>
        /// <param name="queryName">
        /// The HQL query name.
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// The matched entities.
        /// </returns>
        public virtual IList<TE> FindByNamedQueryAndValObj(string queryName, object valObj)
        {
            return this.GetHibernateTemplateEx().FindByNamedQueryAndValueObject<TE>(
                queryName, -1, -1, valObj);
        }

        /// <summary>
        /// Find the first matched entity by providing an HQL and its parameters.
        /// </summary>
        /// <param name="query">
        /// The HQL query.
        /// </param>
        /// <param name="paras">
        /// The query parameters.
        /// </param>
        /// <returns>
        /// The matched entity or null if not found.
        /// </returns>
        public virtual TE FindOne(string query, params object[] paras)
        {
            return this.GetHibernateTemplateEx().FindOne<TE>(query, paras);
        }

        /// <summary>
        /// Find the first matched entity by providing an HQL and 
        /// parameter values from a value object
        /// </summary>
        /// <param name="query">
        /// The HQL query.
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// The matched entity or null if not found.
        /// </returns>
        public virtual TE FindOneByValObj(string query, object valObj)
        {
            return this.GetHibernateTemplateEx().FindOneByValueObject<TE>(query, valObj);
        }

        /// <summary>
        /// Find the first matched entity by providing a named HQL and 
        /// parameter values from a value object
        /// </summary>
        /// <param name="queryName">
        /// The HQL query name.
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// The matched entity or null if not found.
        /// </returns>
        public virtual TE FindOneByNamedQueryAndValObj(string queryName, object valObj)
        {
            return this.GetHibernateTemplateEx().FindOneByNameAndValueObject<TE>(queryName, valObj);
        }

        /// <summary>
        /// Find the first matched entity by providing a named HQL and its parameters.
        /// </summary>
        /// <param name="queryName">
        /// The HQL query name.
        /// </param>
        /// <param name="paras">
        /// The query parameters.
        /// </param>
        /// <returns>
        /// The matched entity or null if not found.
        /// </returns>
        public virtual TE FindOneByNamedQuery(string queryName, params object[] paras)
        {
            return this.GetHibernateTemplateEx().FindOneByName<TE>(queryName, paras);
        }

        /// <summary>
        /// Get number of matched entities by providing an HQL query and its parameter values.
        /// </summary>
        /// <param name="query">
        /// The HQL query.
        /// </param>
        /// <param name="paras">
        /// The query parameter values.
        /// </param>
        /// <returns>
        /// The number of matched entities
        /// </returns>
        public virtual int GetCount(string query, params object[] paras)
        {
            return this.GetHibernateTemplateEx().GetCount(query, paras);
        }

        /// <summary>
        /// Get number of matched entities by providing an HQL query and 
        /// parameter values from a value object.
        /// </summary>
        /// <param name="query">
        /// The HQL query.
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// The number of matched entities.
        /// </returns>
        public virtual int GetCountByValObj(string query, object valObj)
        {
            return this.GetHibernateTemplateEx().GetCountByValueObject(query, valObj);
        }

        /// <summary>
        /// Delete an entity.
        /// </summary>
        /// <param name="entity">
        /// The entity to delete.
        /// </param>
        public virtual void Delete(TE entity)
        {
            this.GetHibernateTemplateEx().Delete(entity);
        }

        /// <summary>
        /// Delete a list of entity.
        /// </summary>
        /// <param name="entities">
        /// The entities to delete.
        /// </param>
        public virtual void DeleteAll(IList<TE> entities)
        {
            this.GetHibernateTemplateEx().DeleteAll(entities);
        }

        /// <summary>
        /// Delete the entity by its key
        /// </summary>
        /// <param name="key">
        /// The entity key.
        /// </param>
        /// <returns>
        /// Number of affected entity ussual 
        /// </returns>
        public virtual int DeleteByKey(TK key)
        {
            return this.GetHibernateTemplateEx().DeleteByKey(this.EntityName, key);
        }

        /// <summary>
        /// Delete entities by their keys
        /// </summary>
        /// <param name="keys">
        /// The entity keys
        /// </param>
        public virtual void DeleteByKeys(IList<TK> keys)
        {
            foreach (var key in keys)
            {
                this.DeleteByKey(key);
            }
        }

        /// <summary>
        /// Batch update entities by providing an HQL query and its paramenter values
        /// </summary>
        /// <param name="query">
        /// The HQL query
        /// </param>
        /// <param name="paras">
        /// The query parameters
        /// </param>
        /// <returns>
        /// Number of affected entities
        /// </returns>
        public virtual int BatchUpdate(string query, params object[] paras)
        {
            return this.GetHibernateTemplateEx().BulkUpdate(query, paras);
        }

        /// <summary>
        /// Batch update entities by providing an HQL query and 
        /// parameter values from a value object.
        /// </summary>
        /// <param name="query">
        /// The HQL query.
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// Number of affected entities.
        /// </returns>
        public virtual int BatchUpdateByValObj(string query, object valObj)
        {
            return this.GetHibernateTemplateEx().BulkUpdateByValueObject(query, valObj);
        }

        /// <summary>
        /// Batch update entities by providing a named HQL query and its paramenter values
        /// </summary>
        /// <param name="queryName">
        /// The named HQL query
        /// </param>
        /// <param name="paras">
        /// The query parameters
        /// </param>
        /// <returns>
        /// Number of affected entities
        /// </returns>
        public virtual int BatchUpdateByNamedQuery(string queryName, params object[] paras)
        {
            return this.GetHibernateTemplateEx().BulkUpdateByName(queryName, paras);
        }

        /// <summary>
        /// Batch update entities by providing a named HQL query and
        /// parameter values from a value object.
        /// </summary>
        /// <param name="queryName">
        /// The named HQL query
        /// </param>
        /// <param name="valObj">
        /// The value object for named parameters.
        /// </param>
        /// <returns>
        /// Number of affected entities
        /// </returns>
        public virtual int BatchUpdateByNamedQueryAndValObj(string queryName, object valObj)
        {
            return this.GetHibernateTemplateEx().BulkUpdateByNameAndValueObject(queryName, valObj);
        }

        /// <summary>
        /// Truncate related table of the entity type, fallback to delete if 
        /// the entity is mapped to many tables
        /// </summary>
        /// <returns>
        /// Number of affected records
        /// </returns>
        public virtual int Truncate()
        {
            return this.GetHibernateTemplateEx().Truncate(this.EntityName);
        }

        /// <summary>
        /// Execute a Hibernate delegate to allow direct interaction with Hibernate session
        /// </summary>
        /// <param name="action">
        /// The Hibernate delegate
        /// </param>
        /// <typeparam name="T">
        /// The return type
        /// </typeparam>
        /// <returns>
        /// The return object
        /// </returns>
        public virtual T Execute<T>(HibernateDelegate<T> action)
        {
            return this.GetHibernateTemplateEx().Execute(action);
        }

        /// <summary>
        /// Execute a Hibernate find delegate to allow direct interaction with Hibernate session
        /// </summary>
        /// <param name="action">
        /// The Hibernate find delegate
        /// </param>
        /// <typeparam name="T">
        /// The returned list element type
        /// </typeparam>
        /// <returns>
        /// The list of returned elements
        /// </returns>
        public virtual IList<T> ExecuteFind<T>(FindHibernateDelegate<T> action)
        {
            return this.GetHibernateTemplateEx().ExecuteFind(action);
        }
    }
}